package cn.kilo.domain.support;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

/**
 * @author kilok
 * 微信支付成功后，微信对回调接口发起的请求，对于请求的签名验证，保证是从微信发出的请求以及保证数据的安全性和真实性
 * 接收回调通知流程：
 * 验签->解密->获取对应body内容
 */
@Slf4j
public class WeChatPayCallBackValidator {

    //应答超时时间 单位分钟
    private static final long RESPONSE_EXPIRED_MINUTES = 5;
    private final Verifier verifier;
    //微信支付的生成的唯一的请求id，通过这个id可以查询对应的支付信息
    private final String requestId;

    private final String body;

    public WeChatPayCallBackValidator(Verifier verifier, String requestId, String body) {
        this.verifier = verifier;
        this.requestId = requestId;
        this.body = body;
    }

    protected static IllegalArgumentException parameterError(String msg, Object... args) {
        msg = String.format(msg, args);
        return new IllegalArgumentException("parameter error:" + msg);
    }

    protected static IllegalArgumentException verifyFail(String msg, Object... args) {
        msg = String.format(msg, args);
        return new IllegalArgumentException("parameter verify fail:" + msg);
    }

    public final boolean validate(HttpServletRequest request) {
        try {
            //处理验证请求参数
            validateParameters(request);

            //构造签名串
            String message = buildMessage(request);

            String serial = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            //验签
            if (verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
                throw verifyFail("serial=[%s] message=[%s] sign=[%s] request-id=[%s]", serial, message, signature, requestId);
            }

        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 验证请求头的参数是否合理
     *
     * @param request
     */
    private void validateParameters(HttpServletRequest request) {

        //请求头对应的信息
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

        String header = null;
        for (String headerName : headers) {
            header = request.getHeader(headerName);
            if (null == headerName) {
                throw parameterError("empty [%s],request-id=[%s]", headerName, requestId);
            }
        }

        //判断请求是否过期
        String timestampStr = header;

        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
                //拒绝过期请求
                throw parameterError("timestamp=[%s] expires ,request-id=", timestampStr, requestId);
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw parameterError("invalid timestamp=[%s] request-id=", timestampStr, requestId);
        }
    }


    /**
     * 构建消息体
     *
     * @param request
     * @return
     */
    private String buildMessage(HttpServletRequest request) {
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        return timestamp + "\n" + nonce + "\n" + body + "\n";
    }

    /**
     * 获取应答的响应体
     *
     * @param response
     * @return
     * @throws IOException
     */
    private String getResponseBody(CloseableHttpResponse response) throws IOException {
        HttpEntity httpEntity = response.getEntity();
        return (null != httpEntity && httpEntity.isRepeatable()) ? EntityUtils.toString(httpEntity) : "";
    }

    public static Map<String, Object> decryptFromResource(String resource, String apiv3Key, Integer type) throws GeneralSecurityException {

        String msg = 1 == type ? "支付成功" : "退款成功";

        log.info("{}回调接口密文解密", msg);

        try {
            //通知数据
            Map<String, Object> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, Object>>() {
            });

            //数据密文
            String ciphertext = String.valueOf(resourceMap.get("ciphertext"));
            //随机串
            String nonce = String.valueOf(resourceMap.get("nonce"));

            //附加数据
            String associateDate = String.valueOf(resourceMap.get("associate_data"));

            log.info("密文:{}", ciphertext);

            AesUtil aesUtil = new AesUtil(apiv3Key.getBytes(StandardCharsets.UTF_8));
            String resourceStr = aesUtil.decryptToString(associateDate.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);

            log.info("{} 回调通知，解密结果{}", msg, resource);

            return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
            });
        } catch (Exception e) {
            throw new RuntimeException("回调参数，解密失败！！！");
        }

    }

}
